/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pl.droidsonroids.gif.gifsample.opengl;

import ohos.agp.components.Image;
import ohos.agp.render.opengl.GLES20;
import ohos.agp.render.opengl.GLES30;
import pl.droidsonroids.gif.GifTexImage2D;

import java.nio.Buffer;

/**
 * GifTexImage2dProgram
 */
public final class GifTexImage2dProgram {
    private final int height;
    private final int width;
    private final int currentFrameDuration;
    private int program;
    private int positionLocation;
    private int textureLocation;
    private int coordinateLocation;
    private final String vertexShaderCode;
    private final String fragmentShaderCode;
    private final Buffer textureBuffer;
    private final Buffer verticesBuffer;
    private Viewport viewport;
    private final GifTexImage2D gifTexImage2D;

    /**
     * Costructor
     *
     * @param gifTexImage2D gifTexImage2D
     * */
    public GifTexImage2dProgram(GifTexImage2D gifTexImage2D) {
        super();
        this.gifTexImage2D = gifTexImage2D;
        this.height = this.gifTexImage2D.getHeight();
        this.width = this.gifTexImage2D.getWidth();
        this.currentFrameDuration = this.gifTexImage2D.getFrameDuration(this.gifTexImage2D.getCurrentFrameIndex());
        this.positionLocation = -1;
        this.textureLocation = -1;
        this.coordinateLocation = -1;
        this.vertexShaderCode = "\n attribute vec4 position;\n attribute vec4 coordinate;\n " +
                "varying vec2 textureCoordinate;\n void main() {\n gl_Position = position;\n " +
                "textureCoordinate = vec2(coordinate.s, 1.0 - coordinate.t);\n }\n ";
        this.fragmentShaderCode = "\n varying mediump vec2 textureCoordinate;\n uniform sampler2D texture;\n " +
                "void main() {\n gl_FragColor = texture2D(texture, textureCoordinate);\n }\n ";
        this.textureBuffer = Utils.toFloatBuffer(new float[]{0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F});
        this.verticesBuffer = Utils.toFloatBuffer(new float[]{-1.0F, -1.0F, 1.0F, -1.0F, -1.0F, 1.0F, 1.0F, 1.0F});
        this.viewport = new Viewport(0, 0, 0, 0);
    }

    public int getHeight() {
        return this.height;
    }

    public int getWidth() {
        return this.width;
    }

    public int getCurrentFrameDuration() {
        return this.currentFrameDuration;
    }

    /**
     * initialize
     *
     * */
    public void initialize() {
        int[] texNames = new int[]{0};

        GLES20.glGenTextures(1, texNames);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texNames[0]);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        int vertexShader = Utils.loadShader(GLES20.GL_VERTEX_SHADER, this.vertexShaderCode);
        int pixelShader = Utils.loadShader(GLES20.GL_FRAGMENT_SHADER, this.fragmentShaderCode);
        this.program = GLES20.glCreateProgram();
        GLES20.glAttachShader(this.program, vertexShader);
        GLES20.glAttachShader(this.program, pixelShader);
        GLES20.glLinkProgram(this.program);
        GLES20.glDeleteShader(pixelShader);
        GLES20.glDeleteShader(vertexShader);
        this.positionLocation = GLES20.glGetAttribLocation(this.program, "position");
        this.textureLocation = GLES20.glGetUniformLocation(this.program, "texture");
        this.coordinateLocation = GLES20.glGetAttribLocation(this.program, "coordinate");
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, this.gifTexImage2D.getWidth(),
            this.gifTexImage2D.getHeight(), 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, (Buffer)null);
        GLES20.glUseProgram(this.program);
        GLES20.glUniform1i(this.textureLocation, 0);
        GLES20.glEnableVertexAttribArray(this.coordinateLocation);
        GLES20.glVertexAttribPointer(this.coordinateLocation, 2, GLES20.GL_FLOAT, false, 0, this.textureBuffer);
        GLES20.glEnableVertexAttribArray(this.positionLocation);
        GLES20.glVertexAttribPointer(this.positionLocation, 2, GLES20.GL_FLOAT, false, 0, this.verticesBuffer);
    }

    /**
     * setDimensions
     *
     * @param screenWidth screenWidth
     * @param screenHeight screenHeight
     * @param scaleType scaleType
     * */
    public void setDimensions(int screenWidth, int screenHeight, Image.ScaleMode scaleType) {
        if (scaleType == Image.ScaleMode.CENTER) {
            viewport = createFitCenterViewport(screenWidth, screenHeight);
        } else {
            viewport = new Viewport(0, 0, screenWidth, screenHeight);
        }
    }

    private Viewport createFitCenterViewport(int screenWidth, int screenHeight) {
        float screenAspectRatio = (float)screenWidth / (float)screenHeight;
        float mediaAspectRation = (float)this.gifTexImage2D.getWidth() / (float)this.gifTexImage2D.getHeight();
        float scaleFactor = screenAspectRatio < mediaAspectRation ? (float)screenWidth / (float)this.gifTexImage2D
                .getWidth() : (float)screenHeight / (float)this.gifTexImage2D.getHeight();
        int viewportWidth = (int)((float)this.gifTexImage2D.getWidth() * scaleFactor);
        int viewportHeight = (int)((float)this.gifTexImage2D.getHeight() * scaleFactor);
        return new Viewport((screenWidth - viewportWidth) / 2, (screenHeight - viewportHeight) / 2,
                viewportWidth, viewportHeight);
    }

    /**
     * Draw
     *
     * */
    public void draw() {
        this.viewport.applyToGlWindow();
        this.gifTexImage2D.glTexSubImage2D(GLES30.GL_TEXTURE_2D, 0);
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
    }

    /**
     * Destroy
     *
     * */
    public void destroy() {
        this.gifTexImage2D.recycle();
    }
}

/**
 * Viewport
 */
final class Viewport {
    private final int offsetX;
    private final int offsetY;
    private final int width;
    private final int height;

    Viewport(int offsetX, int offsetY, int width, int height) {
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.width = width;
        this.height = height;
    }

    /**
     * applyToGlWindow
     */
    public void applyToGlWindow() {
        GLES20.glViewport(this.offsetX, this.offsetY, this.width, this.height);
    }
}
